Skip to content

Add method to track entity state changes from target selectors#148086

Merged
abmantis merged 22 commits into
devfrom
track_entity_changes
Jul 14, 2025
Merged

Add method to track entity state changes from target selectors#148086
abmantis merged 22 commits into
devfrom
track_entity_changes

Conversation

@abmantis
Copy link
Copy Markdown
Member

@abmantis abmantis commented Jul 3, 2025

Proposed change

This PR adds a method to allow triggers to subscribe to events from target selector targets (like floors, labels, etc.).

Requires (and includes commits from): #148087

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:
  • Link to developer documentation pull request:
  • Link to frontend pull request:

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Copy link
Copy Markdown
Contributor

@home-assistant home-assistant Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a merge conflict.

Comment thread homeassistant/helpers/trigger.py Outdated
)
return lambda: None

tracker = TargetSelectorStateChangeTracker(hass, selector_data, job_type, action)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I had a functional implementation, but I wasn't a big fan of it so I moved it into a class. I think it is easier to read now.

The functional version is here: 5d553e5 (#148086)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this function adds value, why don't users just create a TargetSelectorStateChangeTracker themselves?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would also have to create a TargetSelectorData too. I think the function makes it a bit simpler to use (and in a way that is similar to the other event tracking methods).
But I am ok with removing. Let me know if you think it is not worth it.

Comment thread tests/helpers/test_trigger.py Outdated
Comment thread homeassistant/helpers/trigger.py Outdated
Comment thread tests/helpers/test_trigger.py Outdated
Comment thread homeassistant/helpers/trigger.py Outdated
Comment thread homeassistant/helpers/trigger.py Outdated
Comment thread homeassistant/helpers/trigger.py Outdated
Comment thread homeassistant/helpers/trigger.py Outdated
)
return lambda: None

tracker = TargetSelectorStateChangeTracker(hass, selector_data, job_type, action)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this function adds value, why don't users just create a TargetSelectorStateChangeTracker themselves?

Comment thread homeassistant/helpers/trigger.py Outdated
@abmantis abmantis marked this pull request as ready for review July 8, 2025 13:39
@abmantis abmantis requested a review from a team as a code owner July 8, 2025 13:39
@abmantis abmantis requested a review from emontnemery July 8, 2025 17:29
@abmantis abmantis closed this Jul 10, 2025
@abmantis abmantis reopened this Jul 10, 2025
Comment thread tests/helpers/test_target.py Outdated
Comment on lines +497 to +499
# List of entities that should assert a state change when toggled. Contrary to
# entities_to_set_state, entities should be added and removed.
entities_to_assert_change = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this list, and instead pass it to assert_entity_calls_and_reset to make the test easier to follow

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had it implemented like that initially and I don't think it was making the test easier to follow, that is why I changed to this.

With entities_to_assert_change you can clearly see what gets added/removed on each "step" of the test. Also, it ensures that previous entities keep getting tested, unless explicitly removed.
With a full list being passed every it is easier to miss passing an entity, and also makes it a bit harder to see what changed from step to step as you have to read the whole list.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With entities_to_assert_change you can clearly see what gets added/removed on each "step" of the test

This is precisely what I don't think it's good, because to understand what happens at a certain line in the test, all previous lines of the test must be read too

Also, it ensures that previous entities keep getting tested, unless explicitly removed

Why is that? assert_entity_calls_and_reset checks the length of the entities_to_assert_change list, so if an item is forgotten, that check will fail, no?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is precisely what I don't think it's good, because to understand what happens at a certain line in the test, all previous lines of the test must be read too

If the full list is always passed to the method, you still have to check the previous lines to make sure you copied all the relevant entities again. And you have to check all steps, to ensure that you keep only the ones that are still triggered. The way it is currently you can look at each step and see "entity X is removed -> check it" or "entity X is now added -> checked from now one", without having to check every step.

Why is that? assert_entity_calls_and_reset checks the length of the entities_to_assert_change list, so if an item is forgotten, that check will fail, no?

That is true when there is are no bugs, which is what the test should prevent.
For example, lets say currently entity_a is tracked. Then the code gets a new feature where some new target gets tracked, which introduces a bug and entity_a stops being tracked as soon as that target is tracked. On the test, you track the new target as another step.

  • with the current implementation you would have to explicitly add entities_to_assert_change.remove(entity_a) on that step.
  • if instead we pass the full list it would make it easy to miss entity_a in the full list, unless you read the full test again.

Comment thread tests/helpers/test_target.py Outdated
Comment on lines +494 to +496
# List of entities to toggle state during the test. This list should be insert-only
# so that all entities are changed every time.
entities_to_set_state = []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# List of entities to toggle state during the test. This list should be insert-only
# so that all entities are changed every time.
entities_to_set_state = []

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread tests/helpers/test_target.py Outdated

targeted_entity = "light.test_light"

entities_to_set_state.extend([targeted_entity, device_entity, untargeted_entity])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
entities_to_set_state.extend([targeted_entity, device_entity, untargeted_entity])
# List of entities to toggle state during the test. This list should be insert-only
# so that all entities are changed every time.
entities_to_set_state = [targeted_entity, device_entity, untargeted_entity]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is used in toggle_states(), I think it is easier to follow the code if the variable is declared before the method that uses it.
The performance impact of using extend() here isn't really an issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not about performance, it's about the mental load of tracking the mutations of entities_to_set_state while reading the test

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's about the mental load of tracking the mutations of entities_to_set_state while reading the test

I am not sure I get how that mental load gets reduced by moving the declaration of the variable down from the top to here. What is the difference?

Comment thread tests/helpers/test_target.py Outdated
Comment thread tests/helpers/test_target.py Outdated
Comment thread homeassistant/helpers/target.py
Comment thread homeassistant/helpers/target.py Outdated
Comment thread tests/helpers/test_trigger.py
Comment thread tests/helpers/test_target.py Outdated
@abmantis abmantis requested a review from emontnemery July 14, 2025 12:05
Comment thread tests/helpers/test_target.py Outdated
Comment thread tests/helpers/test_target.py Outdated
abmantis and others added 2 commits July 14, 2025 16:06
Co-authored-by: Erik Montnemery <erik@montnemery.com>
@abmantis abmantis requested a review from emontnemery July 14, 2025 15:08
Copy link
Copy Markdown
Contributor

@emontnemery emontnemery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @abmantis 👍

@abmantis abmantis merged commit 1753baf into dev Jul 14, 2025
48 checks passed
@abmantis abmantis deleted the track_entity_changes branch July 14, 2025 18:28
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants